// Copyright (c) 2021 Presto Labs Pte. Ltd.
// Author: xguo

#pragma once

#include <algorithm>
#include <cmath>
#include <cstdint>
#include <deque>
#include <iterator>
#include <memory>
#include <string>
#include <unordered_map>
#include <utility>
#include <vector>

#include "coin2/base/log.h"
#include "coin2/exchange/base/feed/update.h"
#include "coin2/exchange/base/feed_util/book.h"
#include "coin2/exchange/base/log/strategy_logger.h"
#include "coin2/exchange/base/symbology/product.h"
#include "coin2/exchange/base/symbology/product_encyclopedia.h"
#include "coin2/exchange/factory/symbology/product.h"
#include "coin2/exchange/risk/risk_manager.h"
#include "coin2/strategy/hooks/pnl_monitor.h"
#include "coin2/strategy/order_executor.pb.h"
#include "coin2/strategy/order_executor/mm_executor.h"
#include "coin2/strategy/strategy.h"
#include "coin2/strategy/umm/pricer.h"
#include "coin2/strategy/umm/umm.pb.h"

using coin::proto::OrderEvent;
using coin::proto::TradeSide;
using coin2::app::impl::Driver;
using coin2::exchange::base::feed::FeedUpdate;
using coin2::exchange::base::order::IOrderSubsystem;
using coin2::exchange::base::order::OrderUpdate;
using coin2::exchange::base::strategy_util::StrategyLogger;
using coin2::exchange::base::symbology::IProduct;
using coin2::exchange::base::symbology::IProductInfo;
using coin2::exchange::base::symbology::ProductEncyclopedia;
using coin2::exchange::order::RiskManagerConfig;
using coin2::exchange::order::SimConfig;
using coin2::exchange::risk::RiskManager;
using coin2::strategy::PassiveExecutorConfig;
using coin2::strategy::StrategyReporter;
using coin2::strategy::hooks::PnlMonitor;
using coin2::strategy::order_executor::PassiveOrderExecutor;
using coin2::strategy::umm::UmmStrategyConfig;
using coin2::strategy::umm::UmmSubStrategyConfig;

class UmmSubStrategy {
 public:
  UmmSubStrategy(const UmmSubStrategyConfig& config, int64_t ts);
  void Init(Driver* driver);

  const IProduct* trade_product() const {
    // Get trade product.
    return products_.at(0).get();
  }

  double reserve() const {
    // get reserve
    return pricer_->config().reserve;
  }

  MarketExchangeApi mea() const {
    // Get trade mea.
    CHECK(config_.has_mea()) << config_.DebugString();
    return MarketExchangeApi::FromString(config_.mea());
  }

  int64_t timestamp() const { return timestamp_; }

  bool is_ready() const {
    // is sub-strategy ready.
    return pricer_->is_ready();
  }

  IOrderSubsystem* os() {
    // get os
    CHECK(os_);
    return os_;
  }

  void UpdateTimestamp(int64_t ts) {
    CHECK_GT(ts, timestamp_);
    timestamp_ = ts;
  }

  void onBookFeed(const FeedUpdate& upd);
  void onTradeFeed(const FeedUpdate& upd);
  void onAccountOrder(const OrderUpdate& upd);
  void onAccountInfo(const OrderUpdate& upd);

  PassiveOrderExecutor* GetMutableExecutor() {
    // get executor
    return executor_.get();
  }

  double trade_price() const {
    // get trade price
    return pricer_->trade_bbo().midp;
  }

  bool is_ready() {
    // is ready
    return pricer_->is_ready() && os()->is_ready() && num_pos_update_ >= 1;
  }

  void SetRiskManager(RiskManager* risk_manager) { risk_manager_ = risk_manager; }

  PassiveOrderExecutor* Executor() { return executor_.get(); }

 private:
  int64_t start_time_;
  UmmSubStrategyConfig config_;
  RiskManager* risk_manager_;

  std::vector<std::unique_ptr<IProduct>> products_;

  Driver* driver_{nullptr};
  IOrderSubsystem* os_{nullptr};
  std::unique_ptr<xguo::UmmPricer> pricer_;
  std::unique_ptr<PassiveOrderExecutor> executor_;

  ProductEncyclopedia product_cache_;
  const IProductInfo* pi_;

  bool is_trading_futures_{false};
  int64_t timestamp_{0};
  int64_t num_pos_update_{0};
  bool dry_run_{false};
};

class UmmStrategy : public coin2::strategy::IStrategy {
 public:
  UmmStrategy(const std::string& config_file, std::optional<int64_t> ts);

  MarketExchangeApi mea() {
    CHECK(config_.has_mea()) << config_.DebugString();
    return MarketExchangeApi::FromString(config_.mea());
  }
  IOrderSubsystem* os() {
    // get os
    CHECK(os_);
    return os_;
  }

  int64_t timestamp() const { return timestamp_; }

  void UpdateTimestamp(int64_t ts) {
    if (ts > timestamp_) {
      timestamp_ = ts;
    } else {
      timestamp_++;
    }

    constexpr int64_t T = 3600'000'000'000LL;
    if (timestamp_ - last_sim_check_time_ > T) {
      LOG(ERROR) << "current_time: " << timestamp_;
      last_sim_check_time_ = timestamp_;
    }

    for (const auto& pair : strats_) {
      CHECK(pair.second) << pair.first;
      pair.second->UpdateTimestamp(timestamp_);
    }
  }

  void Init(Driver* driver) override;
  void UpdateSimConfig(SimConfig* config);

  void onBookFeed(const FeedUpdate&) override;
  void onTradeFeed(const FeedUpdate&) override;
  void onAccountInfo(const OrderUpdate&) override;
  void onAccountOrder(const OrderUpdate&) override;
  void onOrderLog(const coin::proto::OrderGatewayLog&) override;
  void onOrderSubsystemError(const OrderUpdate&) override;
  void onCleanup(Driver* driver) override;
  void onCleanupAux(Driver* driver, int);

 private:
  int64_t start_time_;
  UmmStrategyConfig config_;

  Driver* driver_{nullptr};
  IOrderSubsystem* os_{nullptr};
  std::unordered_map<std::string, std::unique_ptr<UmmSubStrategy>> strats_;
  std::unique_ptr<RiskManager> risk_manager_;

  // logger/monitor
  std::unique_ptr<StrategyReporter> strat_reporter_;
  std::unique_ptr<PnlMonitor> pnl_monitor_;
  std::unordered_map<const IProduct*, double> reserve_map_;
  int64_t last_print_ts_{0};
  int64_t timestamp_{0};
  int64_t last_sim_check_time_{0};
};
